import { isFunction, isString } from "@vue/shared";
import type { PropType, ExtractPropTypes, CSSProperties } from "vue";
import type { Component } from "vue";
import type Mention from "./Mention.vue";
import type { MentionOption } from "./types";
import { UPDATE_MODEL_EVENT, INPUT_VALUE_EVENT, INPUT_EVENT } from "./event";

export const mutable = <T extends readonly any[] | Record<string, unknown>>(
  val: T
) => val as Mutable<typeof val>;
export type Mutable<T> = { -readonly [P in keyof T]: T[P] };

// Vue's style normalization supports nested arrays
export type StyleValue = string | CSSProperties | Array<StyleValue>;

export type InputAutoSize = { minRows?: number; maxRows?: number } | boolean;
const definePropType = <T>(val: any): PropType<T> => val;
export const iconPropType = definePropType<string | Component>([
  String,
  Object,
  Function,
]);
const filterOption = (pattern: string, option: MentionOption): boolean => {
  const lowerCase = pattern.toLowerCase();
  const label = option.label || option.value;
  return label.toLowerCase().includes(lowerCase);
};

export const mentionProps = {
  /**
   * @description mention options list
   */
  options: {
    type: definePropType<MentionOption[]>(Array),
    default: () => [],
  },
  /**
   * @description prefix character to trigger mentions. The string length must be exactly 1.
   */
  prefix: {
    type: definePropType<string | string[]>([String, Array]),
    default: "@",
    validator: (val: string | string[]) => {
      if (isString(val)) return val.length === 1;
      return val.every((v) => isString(v) && v.length === 1);
    },
  },
  /**
   * @description character to split mentions. The string length must be exactly 1.
   */
  split: {
    type: String,
    default: " ",
    validator: (val: string) => val.length === 1,
  },
  /**
   * @description customize filter option logic.
   */
  filterOption: {
    type: definePropType<false | typeof filterOption>([Boolean, Function]),
    default: () => filterOption,
    validator: (val: any) => {
      if (val === false) return true;
      return isFunction(val);
    },
  },
  /**
   * @description set popup placement
   */
  placement: {
    type: definePropType<"bottom" | "top">(String),
    default: "bottom",
  },
  /**
   * @description whether the dropdown panel has an arrow
   */
  showArrow: Boolean,
  /**
   * @description offset of the dropdown panel
   */
  offset: {
    type: Number,
    default: 0,
  },
  /**
   * @description when backspace is pressed to delete, whether the mention content is deleted as a whole
   */
  whole: Boolean,
  /**
   * @description when backspace is pressed to delete, check if the mention is a whole
   */
  checkIsWhole: {
    type: definePropType<(pattern: string, prefix: string) => boolean>(
      Function
    ),
  },
  /**
   * @description input value
   */
  modelValue: String,
  /**
   * @description whether the dropdown panel of mentions is in a loading state.
   */
  loading: Boolean,
  /**
   * @description custom class name for dropdown panel
   */
  popperClass: {
    type: String,
    default: "",
  },
  /**
   * @description [popper.js](https://popper.js.org/docs/v2/) parameters
   */
  popperOptions: {
    type: Object,
    default: () => ({}),
  },
  inputStyle: {
    type: Object,
    default: () => ({}),
  },
  containerStyle: {
    type: Object,
    default: () => ({}),
  },
  /**
   * @description native `aria-label` attribute
   */
  ariaLabel: String,
  /**
   * @description native `aria-orientation` attribute
   */
  ariaOrientation: {
    type: String,
    values: ["horizontal", "vertical", "undefined"],
  },
  /**
   * @description native `aria-controls` attribute
   */
  ariaControls: String,
  zoom: {
    type: Number,
    default: 1,
  },
};

export const mentionEmits = {
  [UPDATE_MODEL_EVENT]: (value: string) => isString(value),
  [INPUT_EVENT]: (value: string) => isString(value),
  [INPUT_VALUE_EVENT]: (value: string) => isString(value),
  search: (pattern: string, prefix: string) =>
    isString(pattern) && isString(prefix),
  select: (option: MentionOption, prefix: string) =>
    isString(option.value) && isString(prefix),
  focus: (evt: FocusEvent) => evt instanceof FocusEvent,
  blur: (evt: FocusEvent) => evt instanceof FocusEvent,
};

export type MentionEmits = typeof mentionEmits;
export type MentionProps = ExtractPropTypes<typeof mentionProps>;
export type MentionInstance = InstanceType<typeof Mention> & unknown;
